package com.happy3w.bean.xml;

import com.happy3w.bean.json.StringDecoder;
import lombok.Getter;

import java.util.Stack;

public class XmlDecoder {
    private final XmlDecoderListener listener;
    private final StringDecoder stringDecoder = new StringDecoder();
    private final StringBuilder buf = new StringBuilder();

    private XmlState preState = null;
    private XmlState curState = STATE_START;
    @Getter
    private Stack<String> nodeStack = new Stack<>();
    private String attrName;

    public XmlDecoder(XmlDecoderListener listener) {
        this.listener = listener;
    }

    public boolean isInTag() {
        return !nodeStack.isEmpty();
    }

    public void accept(String text) {
        for (char ch : text.toCharArray()) {
            accept(ch);
        }
    }

    public void accept(char in) {
        XmlState newState = curState.accept(in, this);
        if (newState != curState) {
            preState = curState;
            curState = newState;
        }
    }

    private interface XmlState {
        XmlState accept(char in, XmlDecoder decoder);
    }

    private static final XmlState STATE_START = new XmlState() {
        @Override
        public XmlState accept(char in, XmlDecoder decoder) {
            if (in == '<') {
                return STATE_TAG_START;
            }
            decoder.listener.textAccept(in);
            return this;
        }
    };

    private static final XmlState STATE_TAG_START = new XmlState() {
        @Override
        public XmlState accept(char in, XmlDecoder decoder) {
            if (in == '/') {
                return STATE_TAG_END;
            }
            decoder.buf.append(in);
            return STATE_TAG_NAME;
        }
    };

    private static final XmlState STATE_TAG_END = new XmlState() {
        @Override
        public XmlState accept(char in, XmlDecoder decoder) {
            if (in == '>') {
                decoder.listener.nodeEnd(decoder.buf.toString());
                decoder.buf.setLength(0);
                decoder.nodeStack.pop();
                return STATE_START;
            }
            decoder.buf.append(in);
            return this;
        }
    };

    private static final XmlState STATE_TAG_NAME = new XmlState() {
        @Override
        public XmlState accept(char in, XmlDecoder decoder) {
            if (Character.isWhitespace(in) || in == '>') {
                String tagName = decoder.buf.toString();
                decoder.buf.setLength(0);
                decoder.nodeStack.push(tagName);
                decoder.listener.nodeStart(tagName);
                return in == '>' ? STATE_START : STATE_ATTR_NAME;
            }
            decoder.buf.append(in);
            return this;
        }
    };

    private static final XmlState STATE_ATTR_NAME = new XmlState() {
        @Override
        public XmlState accept(char in, XmlDecoder decoder) {
            if (in == '=') {
                decoder.attrName = decoder.buf.toString();
                decoder.buf.setLength(0);
                return STATE_ATTR_VALUE;
            }
            if (Character.isWhitespace(in)) {
                return this;
            }
            if (in == '>') {
                decoder.buf.setLength(0);
                return STATE_START;
            }
            decoder.buf.append(in);
            return this;
        }
    };

    private static final XmlState STATE_ATTR_VALUE = new XmlState() {
        @Override
        public XmlState accept(char in, XmlDecoder decoder) {
            Character ch = decoder.stringDecoder.accept(in);
            if (ch != null) {
                decoder.buf.append(ch);
            } else if (!decoder.stringDecoder.inString()) {
                decoder.listener.attr(decoder.attrName, decoder.buf.toString());
                decoder.buf.setLength(0);
                return STATE_ATTR_NAME;
            }
            return this;
        }
    };
}
